{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit GPSManager;

interface

uses Classes, GPSParser, CameraManager, CPort, Mediate_TLB, DBSchema;

const
  FILE_GPS = 'GPSConfiguration.xml';
  DEFAULT_PORT = 'COM1';
  DEFAULT_BAUD = br9600;
  DEFAULT_INTERVAL = 500; // default interval for updating mediate
  THREAD_TIMEOUT = 50; // delay for polling thread sync.


  UNIT_MILES = 0;
  UNIT_KILOMETERS = 1;
  UNIT_NAUTICAL_MILES = 2;
type
  TAHMGPSManager = class;

  TAHMGPSUpdateThread = class(TThread)
  private
    FOwner: TAHMGPSManager;
  protected
  public
    constructor Create(Owner: TAHMGPSManager);
    procedure Execute; override;
  end;

  TAHMGPSManager = class(TPersistent)
  private
    FComPort: TComPort;
    FFilename: String;
    FMediate: IMediateServer;
    FUpdateEnabled: Boolean;
    FUpdateInterval: Integer;
    FParser: TAHMGPSParser;
    FCamera: TAHMCameraManager;
    FThread: TAHMGPSUpdateThread;
    FUnits: TAHMUnits;
    function GetCOMPort: String;
    procedure SetCOMPort(COMPort: String);
    function GetBaudRate: TBaudRate;
    procedure SetBaudRate(BaudRate: TBaudRate);
    procedure ComPortReceiveChar(Sender: TObject; Count: Integer);
  protected
    procedure LogMessage(Level: LoggingLevel; LogMessage: String);
    procedure ClearMetaData(Key: String);
    procedure SetMetaData(Key: String; Value: Integer); overload;
    procedure SetMetaData(Key: String; Value: String); overload;
    procedure SetMetaData(Key: String; Value: Single); overload;
    function GetMetaData(Key: String): Integer;
    procedure SetUpdateEnabled(Enabled: Boolean);
  public
    constructor Create(Server: IMediateServer);
    destructor Destroy; override;
    function Connect: Boolean;
    procedure Disconnect;
    procedure Reconnect;
    procedure Update;
    procedure Configure;
    procedure TriggerAction(ActionName: String);
    function GetServer(): IMediateServer;
  published
    property Cameras: TAHMCameraManager read FCamera;
    property COMPort: String read GetCOMPort write SetCOMPort;
    property BaudRate: TBaudRate read GetBaudRate write SetBaudRate;
    property UpdateInterval: Integer read FUpdateInterval write FUpdateInterval;
    property Units:TAHMUnits read FUnits;
  end;

implementation

uses SysUtils, MMSystem, PersistenceHelper, GPSConsts, MetaConsts,
     ConfigurationForm;

// TAHMGPSUpdateThread

constructor TAHMGPSUpdateThread.Create(Owner: TAHMGPSManager);
begin
  // Create thread in suspended state
  inherited Create(True);

  // Store owner property for notifications later
  FOwner := Owner;

  // Okay, resume polling thread now
  Resume;
end;

procedure TAHMGPSUpdateThread.Execute;
var
  Delay: Integer;
begin
  while not Terminated do
  begin
    // Get update period from inputs class
    Delay := FOwner.UpdateInterval;

    // Sleep in short intervals to avoid blocking on termination
    while (Delay > THREAD_TIMEOUT) and not Terminated do
    begin
      Sleep(THREAD_TIMEOUT);
      Dec(Delay, THREAD_TIMEOUT);
    end;

    // Wake up manager to perform polling
    if not Terminated then Sleep(Delay);
    if not Terminated then FOwner.Update;
  end;
end;

// TAHMGPSManager

constructor TAHMGPSManager.Create(Server: IMediateServer);
var
  ConfigDir: WideString;
begin
  inherited Create;

  // Store server instance & build configuration filename
  FMediate := Server;
  Server.GetConfigDirectory(ConfigDir);
  FFilename := ConfigDir + FILE_GPS;

  // Create parser & camera manager instance
  FParser := TAHMGPSParser.Create;
  FCamera := TAHMCameraManager.Create(Server);

  // Create comport component & configure defaults
  FComPort := TComPort.Create(nil);
  FComPort.BaudRate := DEFAULT_BAUD;
  FComPort.Port := DEFAULT_PORT;

  // Setup handshaking and flow control to suit GPS usage
  FComPort.FlowControl.XonXoffIn := True;
  FComPort.FlowControl.XonXoffOut := True;
  FComPort.FlowControl.ControlRTS := rtsDisable;
  FComPort.FlowControl.ControlDTR := dtrEnable;
  FComPort.DataBits := dbEight;
  FComPort.StopBits := sbOneStopBit;
  FComPort.Parity.Bits := prNone;

  // Hookup events to receive GPS traffic
  FComPort.OnRxChar := ComPortReceiveChar;
  FComPort.Events := [evRxChar];
  FComPort.SyncMethod := smNone;

  // Configure miscellaneous properties
  FUpdateInterval := DEFAULT_INTERVAL;

  // Load configuration settings
  TAHMPersistenceHelper.LoadObject(Self, FFilename);

  LogMessage(llDebug, 'loaded plugin successfully');
end;

destructor TAHMGPSManager.Destroy;
begin
  Disconnect;

  // Save configuration settings & current values
  TAHMPersistenceHelper.SaveObject(Self, FFilename);

  // Free helper objects
  FComPort.Free;
  FCamera.Free;
  FParser.Free;

  LogMessage(llDebug, 'unloaded plugin successfully');

  inherited;
end;

function TAHMGPSManager.GetCOMPort: String;
begin
  Result := FComPort.Port;
end;

procedure TAHMGPSManager.SetCOMPort(COMPort: String);
begin
  FComPort.Port := COMPort;
end;

function TAHMGPSManager.GetBaudRate: TBaudRate;
begin
  Result := FComPort.BaudRate;
end;

procedure TAHMGPSManager.SetBaudRate(BaudRate: TBaudRate);
begin
  FComPort.BaudRate := BaudRate;
end;

procedure TAHMGPSManager.ComPortReceiveChar(Sender: TObject; Count: Integer);
var
  Str: String;
begin
  // Read content from serial port and pass to parser
  FComPort.ReadStr(Str, Count);
  FParser.Parse(Str, FMediate);
end;

procedure TAHMGPSManager.LogMessage(Level: LoggingLevel; LogMessage: String);
begin
  FMediate.LogMessage(Level, LogMessage);
end;

procedure TAHMGPSManager.ClearMetaData(Key: String);
begin
  FMediate.ClearMetaData(Key);
end;

procedure TAHMGPSManager.SetMetaData(Key: String; Value: Integer);
begin
  FMediate.SetMetaData(Key, IntToStr(Value));
end;

procedure TAHMGPSManager.SetMetaData(Key: String; Value: String);
begin
  FMediate.SetMetaData(Key, Value);
end;

procedure TAHMGPSManager.SetMetaData(Key: String; Value: Single);
begin

  FMediate.SetMetaData(Key, FloatToStrF(Value, ffFixed, 8, 4));
end;

function TAHMGPSManager.GetMetaData(Key: String): Integer;
var
  Value: WideString;
begin
  FMediate.GetMetaData(Key, Value);
  Result := StrToIntDef(Value, 0);
end;

procedure TAHMGPSManager.SetUpdateEnabled(Enabled: Boolean);
begin
  if Enabled <> FUpdateEnabled then
  begin
    // Create/terminate polling thread as required
    if Enabled then
      FThread := TAHMGPSUpdateThread.Create(Self)
    else
    begin
      // Terminate thread and wait for it to stop before freeing
      FThread.Terminate;
      FThread.WaitFor;
      FreeAndNil(FThread);
    end;

    FUpdateEnabled := Enabled;
  end;
end;

function TAHMGPSManager.Connect: Boolean;
begin
  // Set default result - connection succeeded
  Result := True;

  // Skip if already connected to GPS
  if FComPort.Connected then Exit;

  // Connect to GPS with current settings
  try
    LogMessage(llDebug, 'Attempting connection to GPS device');
    FComPort.Open;
    Result := FComPort.Connected;

    // Enable mediate update thread
    if Result then
    begin
      LogMessage(llInfo, 'GPS connected successfully');
      SetUpdateEnabled(True);
    end
    else
      LogMessage(llWarning, 'GPS connection failed');
  except
    on E: Exception do
    begin
      LogMessage(llWarning, 'GPS connection attempt threw an exception');
      LogMessage(llDebug, E.Message);
      Result := False;
    end;
  end;
end;

procedure TAHMGPSManager.Disconnect;
begin
  if FComPort.Connected then
  begin
    // Disable GPS update thread
    SetUpdateEnabled(False);

    // Close COM port
    FComPort.Close;
  end;
end;

procedure TAHMGPSManager.Reconnect;
begin
  Disconnect;
  Connect;
end;

procedure TAHMGPSManager.Update;
begin

  // Ignore unless updates enabled
  if not FUpdateEnabled then
  begin
    ClearMetaData(META_GPS);
    Exit;
  end;

  // If not connected then clear any current values
  if not FComPort.Connected then
  begin
    //TODO... should these values be cached when not running instead?
    ClearMetaData(META_GPS);
    Exit;
  end
  else
  begin
    //Do we have a Satellite fix
    if (FParser.FixStatus = 'A') then
    begin
      // Update current GPS information
      if (FParser.LastCommand = '$GPRMC') then
      begin
        SetMetaData(META_GPS_LONGITUDE, FParser.Longitude);
        SetMetaData(META_GPS_LATTITUDE, FParser.Lattitude);
        //SetMetaData(META_GPS_GRID_COORD, LatDegToDec(FParser.Lattitude,FParser.LattitudeHemisphere));
        //TODO... Not sure we need the command as a Meta tag
        SetMetaData(META_GPS_COMMAND, FParser.LastCommand);
        SetMetaData(META_GPS_SATELITTETIME, FParser.SatelitteTime);
        SetMetaData(META_GPS_FIXSTATUS, FParser.FixStatus);
        SetMetaData(META_GPS_LATTITUDEHEMISPHERE, FParser.LattitudeHemisphere);
        SetMetaData(META_GPS_LONGITUDEHEMISPHERE, FParser.LongitudeHemisphere);
        SetMetaData(META_GPS_SPEED, FParser.Speed);
        SetMetaData(META_GPS_BEARING, FParser.Bearing);
        SetMetaData(META_GPS_CHECKSUM, FParser.Checksum);

        LogMessage(llDebug, Format('Longitude: %12.4f, Lattitude %12.4f,',  [FParser.Longitude, FParser.Lattitude]));
        LogMessage(llDebug, ', LatHem: '+FParser.LattitudeHemisphere+',  LongHem: '+FParser.LongitudeHemisphere+', FIX: '+FParser.FixStatus);
        LogMessage(llDebug,  Format('Speed: %f, Bearing: %f', [FParser.Speed, FParser.Bearing]));
        //LogMessage(llDebug,  Format(' GRID=%f, %f',
         //   [LatDegToDec(FParser.Lattitude,FParser.LattitudeHemisphere),
          //   LongDegToDec(FParser.Longitude,FParser.LongitudeHemisphere)]));
        FCamera.SpeedCameraWarningCheck(FParser);
      end;

      if (FParser.LastCommand = '$GPGGA') then
      begin
        //TODO... Not sure how much use the GGA sentence will be in mediate
      end;
    end
    else
    begin
      ClearMetaData(META_GPS);
    end;
  end;
end;

procedure TAHMGPSManager.Configure;
begin
  // Disable updates whilst configuring (prevents threading issues)
  SetUpdateEnabled(False);
  try
    // Launch configuration form & save changes if required
    if TfrmGPSConfig.Configure(Self) then
    begin
      LogMessage(llDebug, 'updating GPS settings...');
      TAHMPersistenceHelper.SaveObject(Self, FFilename);
      LogMessage(llDebug, 'GPS settings updated');
    end;
  finally
    if FComPort.Connected then SetUpdateEnabled(True);
  end;
end;

procedure TAHMGPSManager.TriggerAction(ActionName: String);
begin
  if ActionName <> '' then
  begin
    LogMessage(llDebug, 'triggering action: ' + ActionName);
    FMediate.TriggerAction(ActionName);
  end;
end;

function TAHMGPSManager.GetServer(): IMediateServer;
Begin
  Result := FMediate;
End;
end.

